1 /** 2 DB Idea Source: 3 http://zetcode.com/db/sqlite/constraints/ 4 */ 5 module test.books; 6 7 version(unittest) 8 { 9 import db_constraints; 10 import test.authors; 11 } 12 13 version(unittest) 14 @ForeignKeyConstraint!( 15 "fk_Books_Authors_AuthorId", 16 ["AuthorId"], 17 "Authors", 18 ["AuthorId"], 19 Rule.cascade, 20 Rule.cascade) 21 class Book 22 { 23 private: 24 int _BookId; 25 string _Title; 26 Nullable!int _AuthorId; 27 public: 28 @PrimaryKeyColumn @NotNull 29 @property int BookId() 30 { 31 return _BookId; 32 } 33 34 @Default!(2) 35 @property Nullable!int AuthorId() 36 { 37 return _AuthorId; 38 } 39 @property void AuthorId(N)(N value) 40 if (isNullable!(int , N)) 41 { 42 setter(_AuthorId, value.to!(Nullable!int)); 43 } 44 this(N)(int BookId_, string Title_, N AuthorId_) 45 if (isNullable!(int, N)) 46 { 47 this._BookId = BookId_; 48 this._Title = Title_; 49 this._AuthorId = AuthorId_; 50 initializeKeyedItem(); 51 } 52 Book dup() 53 { 54 return new Book(this._BookId, this._Title, this._AuthorId); 55 } 56 57 mixin KeyedItem!(); 58 59 } 60 61 version(unittest) 62 class Books 63 { 64 mixin KeyedCollection!(Book); 65 66 static Books GetFromDB() 67 { 68 return new Books([ 69 new Book(1, "Emma", 1), 70 new Book(2, "War and Peace", 2), 71 new Book(3, "Catch XII", 3), 72 new Book(4, "David Copperfield", 4), 73 new Book(5, "Good as Gold", 3), 74 new Book(6, "Anna Karenia", 2) 75 ]); 76 } 77 } 78 unittest 79 { 80 auto authors = Authors.GetFromDB(); 81 auto books = Books.GetFromDB(); 82 83 import std.exception : assertNotThrown; 84 assertNotThrown!ForeignKeyException(books.authors = authors); 85 86 assert(books._authors.contains(1) && !books._authors.contains(5)); 87 authors[1].AuthorId = 5; 88 assert(!books._authors.contains(1) && books._authors.contains(5)); 89 assertNotThrown!ForeignKeyException(books.checkForeignKeys()); 90 91 assert(authors.length == 4); 92 assert(books.length == 6); 93 authors.remove(3); 94 assert(authors.length == 3); 95 assert(books.length == 4); 96 } 97 unittest 98 { 99 auto authors = Authors.GetFromDB(); 100 auto books = Books.GetFromDB(); 101 books.authors = authors; 102 assert(authors.length == 4); 103 assert(books._authors.length == 4); 104 books.authors = null; 105 assert(authors.length == 4); 106 assert(books._authors is null); 107 } 108 109 unittest 110 { 111 auto authors = Authors.GetFromDB(); 112 auto books = Books.GetFromDB(); 113 114 import std.algorithm : filter; 115 import std.exception : assertNotThrown, assertThrown; 116 assertNotThrown!ForeignKeyException(books.authors = authors); 117 118 books.fk_Books_Authors_AuthorId_UpdateRule = Rule.setDefault; 119 static assert(hasDefault!(Book, "AuthorId")); 120 assert(hasDefault!(Book, "AuthorId")); 121 assert(authors.length == 4); 122 assert(books.length == 6); 123 int i = 0; 124 foreach(book; books.byValue.filter!(a => a.AuthorId == 2)) 125 ++i; 126 assert(i == 2); 127 128 i = 0; 129 foreach(book; books.byValue.filter!(a => a.AuthorId == 1)) 130 ++i; 131 assert(i == 1); 132 authors[1].AuthorId = 5; 133 assert(authors.length == 4); 134 assert(books.length == 6); 135 i = 0; 136 foreach(book; books.byValue.filter!(a => a.AuthorId == 2)) 137 ++i; 138 assert(i == 3); 139 140 books.fk_Books_Authors_AuthorId_DeleteRule = Rule.setDefault; 141 authors.remove(3); 142 assert(authors.length == 3); 143 assert(books.length == 6); 144 i = 0; 145 foreach(book; books.byValue.filter!(a => a.AuthorId == 2)) 146 ++i; 147 assert(i == 5); 148 } 149 150 unittest 151 { 152 static assert(hasForeignKeys!(Book)); 153 assert(hasForeignKeys!(Book)); 154 } 155 156 unittest 157 { 158 auto authors = Authors.GetFromDB(); 159 auto books = Books.GetFromDB(); 160 161 books.authors = authors; 162 books.fk_Books_Authors_AuthorId_DeleteRule = Rule.setNull; 163 assert(authors.length == 4); 164 assert(books.length == 6); 165 authors.remove(3); 166 assert(authors.length == 3); 167 assert(books.length == 6); 168 import std.exception : assertNotThrown; 169 assertNotThrown!ForeignKeyException(books.checkForeignKeys()); 170 authors[1].AuthorId = 5; 171 assert(authors.length == 3); 172 assert(books.length == 6); 173 int i = 0; 174 foreach(book; books) 175 { 176 if (book.AuthorId.isNull) 177 { 178 ++i; 179 } 180 } 181 assert(i == 2); 182 books.fk_Books_Authors_AuthorId_UpdateRule = Rule.setNull; 183 authors[5].AuthorId = 1; 184 i = 0; 185 foreach(book; books) 186 { 187 if (book.AuthorId.isNull) 188 { 189 ++i; 190 } 191 } 192 assert(i == 3); 193 } 194 195 unittest 196 { 197 enum fkproperties = 198 `private Authors *_authors; 199 private Authors.key_type _changedAuthorsRow; 200 final @property void authors(ref Authors authors_) 201 { 202 this.authors = null; 203 this._authors = &authors_; 204 this._authors.collectionChanged.connect(&fk_Books_Authors_AuthorId_Changed); 205 checkForeignKeys(); 206 } 207 final @property void authors(typeof(null) n) 208 { 209 if (this._authors !is null) 210 { 211 this._authors.collectionChanged.disconnect(&fk_Books_Authors_AuthorId_Changed); 212 this._authors = null; 213 } 214 } 215 `; 216 static assert(createForeignKeyProperties!(Book) == fkproperties); 217 assert(createForeignKeyProperties!(Book) == fkproperties); 218 } 219 220 unittest 221 { 222 enum fkexceptions = 223 `if (this._authors !is null) 224 { 225 Authors.key_type i; 226 if(a.fk_Books_Authors_AuthorId_key(i)) 227 { 228 enforceEx!ForeignKeyException(this._authors.contains(i), "fk_Books_Authors_AuthorId violation."); 229 } 230 } 231 `; 232 static assert(createForeignKeyCheckExceptions!(Book) == fkexceptions); 233 assert(createForeignKeyCheckExceptions!(Book) == fkexceptions); 234 } 235 236 unittest 237 { 238 enum fkChanged = 239 `Rule fk_Books_Authors_AuthorId_UpdateRule = Rule.cascade; 240 Rule fk_Books_Authors_AuthorId_DeleteRule = Rule.cascade; 241 void fk_Books_Authors_AuthorId_Changed(string propertyName, Authors.key_type item_key) 242 { 243 if (canFind(["AuthorId"], propertyName)) 244 { 245 this._changedAuthorsRow = item_key; 246 } 247 else if (propertyName == "key") 248 { 249 auto changedAuthors = this.byValue.filter!( 250 (Book a) => 251 { 252 Authors.key_type i; 253 return (a.fk_Books_Authors_AuthorId_key(i) ? i == this._changedAuthorsRow : false); 254 }()); 255 final switch (fk_Books_Authors_AuthorId_UpdateRule) with (Rule) 256 { 257 case noAction: 258 break; 259 case restrict: 260 if (!changedAuthors.empty) 261 throw new ForeignKeyException("fk_Books_Authors_AuthorId violation."); 262 break; 263 case setNull: 264 static if (__traits(compiles, 265 (Book a) 266 { 267 a.AuthorId = null; 268 })) 269 { 270 changedAuthors.each!( 271 (Book a) => 272 { 273 a.AuthorId = null; 274 }()); 275 break; 276 } 277 else 278 { 279 throw new ForeignKeyException("fk_Books_Authors_AuthorId. Cannot use Rule.setNull when the member cannot be set to null."); 280 } 281 case setDefault: 282 changedAuthors.each!( 283 (Book a) => 284 { 285 static if (hasDefault!(Book, "AuthorId")) { 286 a.AuthorId = GetDefault!(Book, "AuthorId"); 287 } 288 else 289 { 290 a.AuthorId = typeof(a.AuthorId).init; 291 } 292 }()); 293 break; 294 case cascade: 295 changedAuthors.each!( 296 (Book a) => 297 { 298 a.AuthorId = item_key.AuthorId; 299 }()); 300 break; 301 } 302 } 303 else if (propertyName == "remove") 304 { 305 auto removedAuthors = this.byValue.filter!( 306 (Book a) => 307 { 308 Authors.key_type i; 309 return (a.fk_Books_Authors_AuthorId_key(i) ? i == item_key : false); 310 }()); 311 final switch (fk_Books_Authors_AuthorId_DeleteRule) with (Rule) 312 { 313 case noAction: 314 break; 315 case restrict: 316 if (!removedAuthors.empty) 317 throw new ForeignKeyException("fk_Books_Authors_AuthorId violation."); 318 break; 319 case setNull: 320 static if (__traits(compiles, 321 (Book a) 322 { 323 a.AuthorId = null; 324 })) 325 { 326 removedAuthors.each!( 327 (Book a) => 328 { 329 a.AuthorId = null; 330 }()); 331 break; 332 } 333 else 334 { 335 throw new ForeignKeyException("fk_Books_Authors_AuthorId. Cannot use Rule.setNull when the member cannot be set to null."); 336 } 337 case setDefault: 338 removedAuthors.each!( 339 (Book a) => 340 { 341 static if (hasDefault!(Book, "AuthorId")) { 342 a.AuthorId = GetDefault!(Book, "AuthorId"); 343 } 344 else 345 { 346 a.AuthorId = typeof(a.AuthorId).init; 347 } 348 }()); 349 break; 350 case cascade: 351 removedAuthors.each!( 352 (Book a) => 353 { 354 this.remove(a.key); 355 }()); 356 break; 357 } 358 } 359 } 360 `; 361 static assert(createForeignKeyChanged!(Book) == fkChanged); 362 assert(createForeignKeyChanged!(Book) == fkChanged); 363 } 364 365 unittest 366 { 367 enum fkproperties = 368 `final bool fk_Books_Authors_AuthorId_key(out Authors.key_type aKey) 369 { 370 bool result; 371 static if ( 372 is(typeof(aKey.AuthorId) == typeof(this.AuthorId)) 373 ) 374 { 375 aKey.AuthorId = this.AuthorId; 376 result = true; 377 } 378 else static if (__traits(compiles, 379 (Book b) 380 { 381 if (b.AuthorId.isNull == true) { } 382 })) 383 { 384 if ( 385 !this.AuthorId.isNull 386 ) 387 { 388 aKey.AuthorId = this.AuthorId; 389 result = true; 390 } 391 else 392 { 393 result = false; 394 } 395 } 396 else 397 { 398 static assert(false, "Column type mismatch for fk_Books_Authors_AuthorId."); 399 } 400 return result; 401 } 402 `; 403 404 static assert(createForeignKeyPropertyConverter!(Book) == fkproperties, createForeignKeyPropertyConverter!(Book)); 405 assert(createForeignKeyPropertyConverter!(Book) == fkproperties, createForeignKeyPropertyConverter!(Book)); 406 } 407 408 unittest 409 { 410 auto authors = Authors.GetFromDB(); 411 auto books = Books.GetFromDB(); 412 413 books.authors = authors; 414 books.fk_Books_Authors_AuthorId_UpdateRule = Rule.restrict; 415 books.fk_Books_Authors_AuthorId_DeleteRule = Rule.restrict; 416 417 import std.exception : assertThrown; 418 419 assertThrown!ForeignKeyException(authors.remove(3)); 420 421 assert(!authors.contains(18)); 422 assertThrown!ForeignKeyException(books[1].AuthorId = 18); 423 }